﻿#region Copyright

// All Material Copyright © 2010 Dynamic Concepts Development Corp., All Rights Reserved.
//      Portions Copyright © 1984-2009 Dynamic Concepts Development Corp., All Rights Reserved.
// For further information please contact: SoftwareSupport@dynconcepts.com
// Dynamic Concepts Development Corp.  311 W 57th St. Suite #422, New York, NY 10019

#endregion

#region Using References

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Threading;
using DynConDevCorp.OpenArch.SupportLibrary.ApplicationInfrastructure;

#endregion

namespace DynConDevCorp.OpenArch.SupportLibrary.ParallelExecution.Events
{
    public enum ParallelEventDispatchMode
    {
        AutoAsync,
        VirtualSync,
        CrossThreadDirect,
        ForceAsync
    }

    public class ParallelEvent<TARGS> : IParallelEvent<TARGS> where TARGS : EventArgs
    {
        #region Constructors

        public ParallelEvent()
        {
            r_EventCreatorDispatcher = Dispatcher.CurrentDispatcher;
            DispatchMode = ParallelEventDispatchMode.VirtualSync;
            ParallelExecutionBase.OwnerListEmptied += OwnerListEmptied;
        }

        public ParallelEvent(ParallelEventDispatchMode dispatchMode)
        {
            r_EventCreatorDispatcher = Dispatcher.CurrentDispatcher;
            DispatchMode = dispatchMode;
            ParallelExecutionBase.OwnerListEmptied += OwnerListEmptied;
        }

        #endregion

        #region Public Properies and Indexers

        public Type ArgumentType
        {
            get { return typeof (TARGS); }
        }

        public ParallelEventDispatchMode DispatchMode { get; set; }

        public Dispatcher EventCreatorDispatcher
        {
            get { return r_EventCreatorDispatcher; }
        }

        #endregion

        #region Public Methods

        public IParallelEventTarget Add(EventHandler<TARGS> internalHandler)
        {
            return AddImpl(new ParallelEventHandler<TARGS>(internalHandler), null, null, DispatchMode);
        }

        public IParallelEventTarget Add(EventHandler<TARGS> internalHandler, string name)
        {
            return AddImpl(new ParallelEventHandler<TARGS>(internalHandler), null, name, DispatchMode);
        }

        public IParallelEventTarget Add(ParallelEventHandler<TARGS> theHandler)
        {
            return AddImpl(theHandler, null, null, DispatchMode);
        }

        public IParallelEventTarget Add(ParallelEventHandler<TARGS> theHandler, string name)
        {
            return AddImpl(theHandler, null, name, DispatchMode);
        }

        public IParallelEventTarget Add(ParallelEventHandler<TARGS> theHandler, string name, ParallelEventDispatchMode dispatchMode)
        {
            return AddImpl(theHandler, null, name, dispatchMode);
        }

        public IParallelEventTarget Add(ParallelEventHandler<TARGS> theHandler, ParallelEventHandler<TARGS> completionHandler)
        {
            return AddImpl(theHandler, completionHandler, null, DispatchMode);
        }

        public IParallelEventTarget Add(ParallelEventHandler<TARGS> theHandler, ParallelEventHandler<TARGS> completionHandler, string name)
        {
            return AddImpl(theHandler, completionHandler, name, DispatchMode);
        }

        public IParallelEventTarget AddImpl(ParallelEventHandler<TARGS> theHandler, ParallelEventHandler<TARGS> completionHandler, string name, ParallelEventDispatchMode dispatchMode)
        {
            ParallelEventTarget<TARGS> theTarget = new ParallelEventTarget<TARGS>(theHandler, completionHandler, r_EventCreatorDispatcher, name, this, dispatchMode);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Add({0})", theTarget.ID);
            r_Targets.Add(theTarget);
            return theTarget;
        }

        public void Execute(object sender, TARGS args)
        {
            ExecuteImpl(sender, args, null);
        }

        public void Execute(object sender, TARGS args, EventHandler<TARGS> completionHandler)
        {
            ExecuteImpl(sender, args, new ParallelEventHandler<TARGS>(completionHandler));
        }

        public void Execute(object sender, TARGS args, ParallelEventHandler<TARGS> completionHandler)
        {
            ExecuteImpl(sender, args, completionHandler);
        }

        public void ExecuteImpl(Object sender, TARGS args, ParallelEventHandler<TARGS> completionHandler)
        {
            int executionCount = Interlocked.Increment(ref m_ExecutionCount);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Execute() - Starting ExecutionCount={0}", executionCount);
            if (!ReferenceEquals(Dispatcher.CurrentDispatcher, r_EventCreatorDispatcher))
            {
                if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Dispatching to Creation Thread....");
                DispatchExecuteToCreationThread(sender, args, completionHandler);
                return;
            }
            if (completionHandler != null)
            {
                m_CompletionTarget = new ParallelEventTarget<TARGS>(completionHandler, null, Dispatcher.CurrentDispatcher, "Completion", this, DispatchMode);
            }
            else
            {
                m_CompletionTarget = null;
            }
            List<ParallelEventTarget<TARGS>> targets = new List<ParallelEventTarget<TARGS>>(r_Targets); // So we can add/remove during execution
            m_OutstandingTargetCount += targets.Count;

            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Dispatching Targets Count:{0}", targets.Count);
            int index = 0;
            foreach (ParallelEventTarget<TARGS> target in targets)
            {
                if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Processing Target #{0}: {1} - Started", index, target.ID);
                target.ExecuteInternalHandler(sender, args);
                if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Processing Target #{0}: {1} - Completed", index, target.ID);
                ++index;
            }
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Dispatching Targets Completed!");
            executionCount = Interlocked.Decrement(ref m_ExecutionCount);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Execute() - Completed ExecutionCount={0}", executionCount);
        }

        public void Remove(EventHandler<TARGS> theHandler)
        {
            Remove(new ParallelEventHandler<TARGS>(theHandler));
        }

        public void Remove(ParallelEventHandler<TARGS> theHandler)
        {
            foreach (ParallelEventTarget<TARGS> target in r_Targets)
            {
                if (target.Matches(theHandler))
                {
                    if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Remove({0})", target.Name);
                    r_Targets.Remove(target);
                    return;
                }
            }
            throw new ArgumentException("Matching Handler NOT found!", "theHandler");
        }

        public void TargetCompleted(IParallelEventTarget target, object sender, EventArgs rawArgs)
        {
            TARGS args = (TARGS) rawArgs;
            int outstandingCount = Interlocked.Decrement(ref m_OutstandingTargetCount);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "TargetCompleted({0})  Outstanding={1}", target.Name, outstandingCount);
            if (outstandingCount == 0)
            {
                if (m_CompletionTarget != null)
                {
                    m_CompletionTarget.ExecuteInternalHandler(sender, args);
                }
            }
        }

        public void WaitForIdle()
        {
            if (ParallelExecutionBase.IsIdle(this))
                return;
            m_WaitForIdleSemaphore = new Semaphore(0, 1);
            ParallelExecutionBase.OwnerListEmptied += OwnerListEmptiedRecieved;
            bool done = false;
            // NOTE: Current Completion Handler Implementation will cause this to timeout!
            DateTime timeOut = DateTime.UtcNow + TimeSpan.FromMilliseconds(2500);
            while (!done)
            {
                done |= ParallelExecutionBase.IsIdle(this);
                if (!done)
                {
                    bool result = m_WaitForIdleSemaphore.WaitOne(TimeSpan.FromMilliseconds(5));
                    done |= result;
                }
                done |= (DateTime.UtcNow > timeOut);
                if (m_ExecutionCount > 0) done = false;
                if (m_OutstandingTargetCount > 0) done = false;
                if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "WaitForIdle(), IsIdle={0}, ExecutionCount={1}, Outstanding={2}", ParallelExecutionBase.IsIdle(this), m_ExecutionCount, m_OutstandingTargetCount);
            }
            ParallelExecutionBase.OwnerListEmptied -= OwnerListEmptiedRecieved;
            m_WaitForIdleSemaphore = null;
        }

        #endregion

        #region Private Methods

        private void DispatchExecuteToCreationThread(object sender, TARGS args, ParallelEventHandler<TARGS> completionHandler)
        {
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "DispatchExecuteToCreationThread() - Starting");
            ParallelEventTarget<ExecuteArgs> executeTarget = new ParallelEventTarget<ExecuteArgs>(new ParallelEventHandler<ExecuteArgs>(DispatchedExecute), null, r_EventCreatorDispatcher, "Event", this, DispatchMode);
            ExecuteArgs executeArgs = new ExecuteArgs(sender, args, completionHandler);
            executeTarget.InvokeOnThread(r_EventCreatorDispatcher, sender, executeArgs, DispatchMode);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "DispatchExecuteToCreationThread.Execute() - Completed");
        }

        private void DispatchedExecute(object sender, ExecuteArgs args)
        {
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "DispatchedExecute() - Starting ExecutionCount={0}", m_ExecutionCount);
            Execute(args.Sender, args.Args, args.CompletionHandler);
            int executionCount = Interlocked.Decrement(ref m_ExecutionCount);
            if (m_EnableLogging) QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "DispatchedExecute() - Completed ExecutionCount={0}", executionCount);
        }

        private void OwnerListEmptied(Object sender, EventArgs args)
        {
            if (sender != this)
                return;
            QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "OwnerListEmptied()");
        }

        private void OwnerListEmptiedRecieved(Object sender, EventArgs args)
        {
            if (sender != this)
                return;
            Semaphore semaphore = m_WaitForIdleSemaphore;
            if (semaphore != null)
            {
                semaphore.Release();
            }
        }

        #endregion

        #region Static Fields and Constants

        private const bool c_EnableLogging = true;

        #endregion

        #region Instance Fields (ReadOnly)

        private readonly Dispatcher r_EventCreatorDispatcher;

        private readonly List<ParallelEventTarget<TARGS>> r_Targets = new List<ParallelEventTarget<TARGS>>();

        #endregion

        #region Instance Fields (Mutable)

        private ParallelEventTarget<TARGS> m_CompletionTarget;
        private bool m_EnableLogging = c_EnableLogging;
        private int m_ExecutionCount;
        private int m_OutstandingTargetCount;
        private Semaphore m_WaitForIdleSemaphore;

        #endregion

        #region Nested type: CrossThreadExecuteHandler

        private delegate void CrossThreadExecuteHandler(object sender, TARGS args, ParallelEventHandler<TARGS> completionHandler);

        #endregion

        #region Nested type: ExecuteArgs

        public class ExecuteArgs : EventArgs
        {
            #region Constructors

            public ExecuteArgs(object sender, TARGS args, ParallelEventHandler<TARGS> completionHandler)
            {
                Sender = sender;
                Args = args;
                CompletionHandler = completionHandler;
            }

            #endregion

            #region Instance Fields (ReadOnly)

            public readonly TARGS Args;
            public readonly ParallelEventHandler<TARGS> CompletionHandler;
            public readonly object Sender;

            #endregion
        }

        #endregion
    }
}